/*
 * Heap.cpp
 *
 *  Created on: Oct 20, 2009
 *      Author: swapnil
 */

#include "Heap.h"
#include <malloc.h>
#include "../DiskSpaceManager/Database.h"
#include <iostream>
#include <string.h>
#include "../BufferManager/BufferManager.h"
//#include "../yADBMS.h"

extern BufferManager *buffer;
extern int noOfBufferFrame;
extern Database *db;


using namespace std;

const static int firstDpPage = 4;

Status Heap::initDb(int init) {

	/*Status status;
	if (init == 0) {
		database = new Database("DB.txt", 20, status);
		cout << "\n\nDatabase created " << endl;
		initDbHeader(); // initialising first pages which are DbHeaderPage, Syscol page, SysTablePage
		return status;

	}

	else {
		database = new Database("DB.txt", status);
	}*/
	initDbHeader();
	return ok;

}
;

void Heap::exitDb() {

	//database->~Database();
}

Status Heap::initDbHeader() {

	cout << "Now initialising Database\n";
	Page* initDp;
	int dbHPageNum, sysColPageNum, sysTabPageNum, sysIndPageNum;
	dp* DpPage = (dp*) malloc(sizeof(dp));

	initDp = getNewPage(dbHPageNum); //page allocated for first header page
	DpPage = (dp*) initDp->pageData;
	cout << "Page allocated for DB header: " << dbHPageNum << endl;
	sysColPageNum = initFirstDpPage(); // page allocated for SysCol
	cout << "Page allocated for sysCol: " << sysColPageNum << endl;
	sysTabPageNum = initFirstDpPage(); // page allocated for SysTable
	cout << "Page allocated for sysTable: " << sysTabPageNum << endl;
	sysIndPageNum = initFirstDpPage(); // page allocated for SysTable
	cout << "Page allocated for sysTable: " << sysIndPageNum << endl;

	DpPage->dirElement[0].pageNumber = sysColPageNum; // updating first De element of first Header page with systable
	DpPage->dirElement[1].pageNumber = sysTabPageNum; // updating second De element of first header page with syscol
	DpPage->dirElement[3].pageNumber = sysIndPageNum;
//	DpPage->dirElement[0].freeSpace = 4096;
//	DpPage->dirElement[1].freeSpace = 4096;
	writePage(initDp, dbHPageNum);
	return ok;
};

int Heap::initFirstDpPage() {

	Page *initDp, *readDp;
	dp *HPage;
	dpH *DpH = (dpH*) malloc(sizeof(dpH));
	de *dirE = (de*) malloc(sizeof(de));
	int thisDpPageNumber;
	initDp = getNewPage(thisDpPageNumber); // page allocated for first Dp page of the Database pages
	cout << "Page allocated for first Dp page in Dp chain: "
			<< thisDpPageNumber << "\n\n"; //  Dp page will contain several De elements each pointing to one Db page
	DpH->maxEmptySpace = PAGE_SIZE;
	DpH->nextDp = -1;
	DpH->numberOfDe = MAX_DES;
	(*(dpH*) (initDp->pageData)) = *DpH;
	dirE->pageNumber = -1;
	dirE->freeSpace = PAGE_SIZE;

	for (unsigned int iter = 0; iter < MAX_DES; iter++) {
		(*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))) = *dirE;

	}
	writePage(initDp, thisDpPageNumber);
//	readDp = getPage(4);
//	HPage = (dp*) readDp->pageData;
//	(*(de*) (readDp->pageData + sizeof(dpH) + sizeof(de) * 2)).pageNumber
//			= thisDpPageNumber; // updating the DE element of header page with the
//	writePage(readDp, 4); // page number of this Dp page as the first page of Dp chain
	return thisDpPageNumber;
}
;

//TODO use below function to create Dp chain
Status Heap::initDpPage() {

	Page *initDp, *readDp;
	dp *HPage;
	dpH *DpH = (dpH*) malloc(sizeof(dpH));
	de *dirE = (de*) malloc(sizeof(de));
	int thisDpPageNumber, firstDpPageNum, nextDpNum = 0, prevDpNum = 0;

	initDp = getNewPage(thisDpPageNumber); // page allocated for first Dp page of the Database pages
	cout << "Allocated page number: " << thisDpPageNumber << endl; //  Dp page will contain several De elements each pointing to one Db page
	DpH->maxEmptySpace = PAGE_SIZE;
	DpH->numberOfDe = MAX_DES;
	(*(dpH*) (initDp->pageData)) = *DpH;
	dirE->pageNumber = -1;
	dirE->freeSpace = PAGE_SIZE;

	for (unsigned int iter = 0; iter < MAX_DES; iter++) {
		(*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))) = *dirE;
	}

	firstDpPageNum = getFirstDpNum();
	readDp = getPage(firstDpPageNum);
	nextDpNum = (*(dpH*) (readDp->pageData)).nextDp;

	while (nextDpNum != -1) {
		prevDpNum = nextDpNum;
		readDp = getPage(nextDpNum);
		nextDpNum = (*(dpH*) (readDp->pageData)).nextDp;
	}
	cout << "prevDpNum: " << prevDpNum << "\tnextDpNum: " << nextDpNum << "\n";
	if (prevDpNum == 0) {
		cout << "inside if \n";
		(*(dpH*) (readDp->pageData)).nextDp = thisDpPageNumber;
		(*(dpH*) (initDp->pageData)).nextDp = -1;
		writePage(readDp, firstDpPageNum);
	} else {
		cout << "inside else \n";
		readDp = getPage(prevDpNum);
		(*(dpH*) (readDp->pageData)).nextDp = thisDpPageNumber;
		(*(dpH*) (initDp->pageData)).nextDp = -1;
		writePage(readDp, firstDpPageNum);
	}

	writePage(initDp, thisDpPageNumber);
	return ok;

}

PageId Heap::insertRecord(const char *recPtr,int thisFirstDpPage) {

	Page* initDp, *initDb;
	PageId pid;
	//int thisDbPageNum, firstDpPage = getFirstDpNum();
	int thisDbPageNum, firstDpPage = thisFirstDpPage;
	int prevfreeSpace, spaceUsed;
	unsigned int iter, loop;
	initDp = getPage(firstDpPage);

	for (iter = 0; iter < MAX_DES; iter++) {
		//cout << "Value of iter here: "<< iter << endl;
		prevfreeSpace = (*(de*) (initDp->pageData + sizeof(dpH) + iter
				* sizeof(de))).freeSpace;
		if (((*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).pageNumber
				== -1)) { // condition where the page is being created
			//cout << "inside if\n";
			initDb = getNewPage(thisDbPageNum);
			//cout  << "free space in this DE element"<< (*(de*)(initDp->pageData +sizeof(dpH) + iter*sizeof(de))).freeSpace << "\n";
			//cout << "Allocated Page is: " << thisDbPageNum <<endl;
			(*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).pageNumber
					= thisDbPageNum;
			spaceUsed = headerSize + slotSize + sizeof(int) + (int) strlen(
					recPtr);
			(*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).freeSpace
					= prevfreeSpace - spaceUsed;
			//cout  << "free space now in this DE element"<< (*(de*)(initDp->pageData +sizeof(dpH) + iter*sizeof(de))).freeSpace << "\n";

			/*
			 * header initialisation for this DB
			 * */
			header_t *dbH = (header_t*) malloc(sizeof(dbH));
			dbH->freeSpace = prevfreeSpace - spaceUsed;
			dbH->timeStamp = getTimeStamp();
			*(header_t*) (initDb->pageData) = *dbH;

			/*
			 * slot initialisation for this db
			 * */
			slot_t *dbslot = (slot_t*) malloc(slotSize);
			dbslot->offset = 0;
			dbslot->length = strlen(recPtr);
			*(slot_t*) (initDb->pageData + PAGE_SIZE - slotSize - sizeof(int))
					= *dbslot;

			/*
			 *  inserting  data now
			 * */
			for (loop = 0; loop < strlen(recPtr); loop++) {
				*(char*) (initDb->pageData + headerSize + loop) = *(recPtr
						+ loop);
			}
			//++loop;
			//*(char*)(initDb->pageData + sizeof(header_t) + loop) = '\0';

			*(int*) (initDb->pageData + PAGE_SIZE - sizeof(int)) = 1; // initialise slot count to 1
			//cout << "initial number of slots are " << *(int*)(initDb->pageData + PAGE_SIZE - sizeof(int)) << endl;
			pid.pageno = thisDbPageNum;
			pid.slotno = 1;

			writePage(initDb, thisDbPageNum);
			writePage(initDp, firstDpPage);
			return pid;
			break;
		}

		else if ((prevfreeSpace - slotSize) > (int) strlen(recPtr)) {

			/*TODO
			 *1. Changing all the free space variables i.e DE.freespace, DB.freespace ----------------->done
			 *2. Create a new DB page if all the slots are full in the DB page  ----------------------------->done
			 *3. Create New DP page if all the DE entries in the DP page are full
			 * */

			//cout << "inside else\n";
			prevfreeSpace = (*(de*) (initDp->pageData + sizeof(dpH) + iter
					* sizeof(de))).freeSpace;
			//cout  << "free space in this DE element"<< prevfreeSpace << "\n";
			spaceUsed = slotSize + (int) strlen(recPtr);
			int thisDbPageNum = (*(de*) (initDp->pageData + sizeof(dpH) + iter
					* sizeof(de))).pageNumber;
			int dataOffset, slotCount;

			/*
			 * Here the work to be done are
			 * 1. change the DE which is pointing this DB page
			 * 2. change DB header of the actual page
			 * 3. create a new slot
			 * 4. write data
			 * 5. write the page again in memory
			 * */
			//   important -----> allocate memory to all the elements before you read the page otherwise there will error in reading data
			(*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).freeSpace
					= prevfreeSpace - spaceUsed;
			header_t *tempHead = (header_t*) malloc(headerSize);
			tempHead->freeSpace = prevfreeSpace - spaceUsed;
			tempHead->timeStamp = getTimeStamp();
			slot_t* tempSlot = (slot_t*) malloc(slotSize);
			tempSlot->length = strlen(recPtr);
			initDb = getPage(thisDbPageNum);
			slotCount = (*(int*) (initDb->pageData + PAGE_SIZE - sizeof(int)));
			//cout << "slotCount" << slotCount << "\n";
			/*********finding free slot i.e checking whether the free slot is at the end of slot directory or in between*********************************************/

			/* This condition is only met in case of slot deletion */

			int tempSlotOff, newDataOff;
			unsigned int tempSlotLen, dataLen = strlen(recPtr);
			for (int iter = 1; iter <= slotCount; iter++) {

				tempSlotOff = (*(slot_t*) (initDb->pageData + PAGE_SIZE
						- sizeof(int) - slotSize * (iter))).offset;
				tempSlotLen = (*(slot_t*) (initDb->pageData + PAGE_SIZE
						- sizeof(int) - slotSize * (iter))).length;

				if (tempSlotOff == -1 && dataLen <= tempSlotLen) {
					cout << "Got empty slot at slot Number :" << (iter)
							<< " of slot length " << tempSlotLen << "\n";
					pid.slotno = iter;
					if (iter == 1) {
						newDataOff = 0;
						(*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
								- slotSize * (iter))).offset = newDataOff;
					} else {
						newDataOff
								= (*(slot_t*) (initDb->pageData + PAGE_SIZE
										- sizeof(int) - slotSize * (iter - 1))).offset
										+ (*(slot_t*) (initDb->pageData
												+ PAGE_SIZE - sizeof(int)
												- slotSize * (iter - 1))).length;
						(*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
								- slotSize * (iter))).offset = newDataOff;
					}
					//cout << "Offset for this slot is: " << newDataOff << "\n";
					//cout << "Length of data is: " << dataLen << "\n";
					for (unsigned int iter = 0; iter < dataLen; iter++) {
						*(char*) (initDb->pageData + headerSize + newDataOff
								+ iter) = *(recPtr + iter);
					}
					for (unsigned int iter = (dataLen); iter < tempSlotLen; iter++) {
						*(char*) (initDb->pageData + headerSize + newDataOff
								+ iter) = '\0';
					}

					writePage(initDb, thisDbPageNum);
					writePage(initDp, firstDpPage);
					pid.pageno = thisDbPageNum;
					return pid;

				}

				else if (tempSlotOff == -1 && strlen(recPtr) > tempSlotLen) {
					//cout << "Slot is not sufficient for this data\n";
				}
			}

			/************************************************************************************************************************************************/

			/*
			 *  Finding offset for the new slot.
			 *  Offset can be found out using the offset + length of the last slot being filled
			 * */
			dataOffset = (*(slot_t*) (initDb->pageData + PAGE_SIZE
					- sizeof(int) - slotSize * (slotCount))).offset
					+ (*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
							- slotSize * (slotCount))).length;
			tempSlot->offset = dataOffset;
			//cout << "new data's offset is : " << dataOffset << "\n";
			/*
			 * Writing out the new slot and modified header
			 * */
			slotCount++;
			(*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
					* slotCount)) = *tempSlot;
			(*(header_t*) (initDb->pageData)) = *tempHead;
			(*(int*) (initDb->pageData + PAGE_SIZE - sizeof(int))) = slotCount;

			/*
			 * Writing data to the location
			 * */
			for (loop = 0; loop < strlen(recPtr); loop++) {
				*(char*) (initDb->pageData + headerSize + dataOffset + loop)
						= *(recPtr + loop);
			}

			writePage(initDb, thisDbPageNum);
			writePage(initDp, firstDpPage);
			pid.pageno = thisDbPageNum;
			pid.slotno = slotCount;
			return pid;
			break;
		}

		else if (((*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).freeSpace
				+ (int) sizeof(slot_t)) < (int) strlen(recPtr)) {
			cout << "Creating new Db Page now";
		}
	}

}
;

Status Heap::deleteRecord(PageId pid) {

	int pageNum, slotNum;
	char* rec;
	Page* initDb;
	pageNum = pid.pageno;
	slotNum = pid.slotno;
	initDb = getPage(pageNum);

	if ((*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
			* slotNum)).offset != -1) {
		cout << "Record found at ";
		cout << "slot:" << pid.slotno << " and " << "Page Number:"
				<< pid.pageno;
		cout << "is : ";

		rec = getRecord(pid);

		if (rec == NULL)
			cout << "no record exist at this page and slot \n";
		else if (rec != NULL) {
			for (unsigned int iter = 0; iter < strlen(rec); iter++) {
				cout << *(rec + iter);
			}
			cout << "\n";
		}
		(*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
				* slotNum)).offset = -1;

		(*(header_t*) (initDb->pageData)).freeSpace
				= (*(header_t*) (initDb->pageData)).freeSpace
						- (*(slot_t*) (initDb->pageData + PAGE_SIZE
								- sizeof(int) - slotSize * slotNum)).length;

	}

	writePage(initDb, pageNum);
	cout << "data deleted successfully\n";
	return ok;
}

Status Heap::updateRecord(PageId pid, char* newValue) {

	int pageNum, slotNum, iter, slotLen, slotOff, loop;
	char* rec;
	Page* initDb;
	pageNum = pid.pageno;
	slotNum = pid.slotno;
	initDb = getPage(pageNum);
	slotLen = (*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
			- slotSize * slotNum)).length;
	slotOff = (*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
			- slotSize * slotNum)).offset;

	if ((*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
			* slotNum)).offset != -1) {
		cout << "Record found at ";
		cout << "Slot:" << pid.slotno << " and " << " Page Number: "
				<< pid.pageno << "\n";

		rec = getRecord(pid);

		if (rec == NULL)
			cout << "no record exist at this page and slot \n";
		else if (rec != NULL) {
			cout << "Previously record was: ";
			for (iter = 0; iter < (int) strlen(rec); iter++) {
				cout << (*(char*) (initDb->pageData + headerSize + slotOff
						+ iter));
				//cout << *(rec + iter);
			}
			cout << "\n";

			if (slotLen >= (int) strlen(newValue)) {

				// updation of record  done here

				for (iter = 0; iter < (int) strlen(newValue); iter++) {
					(*(char*) (initDb->pageData + headerSize + slotOff + iter))
							= *(newValue + iter);
				}
				for (loop = iter; loop < slotLen; loop++) {
					(*(char*) (initDb->pageData + headerSize + slotOff + iter))
							= '\0';
				}

				(*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
						- slotSize * slotNum)).length = (int) strlen(newValue);
				cout << "Now Record changed to: ";
				for (iter = 0; iter < slotLen; iter++) {
					cout << (*(char*) (initDb->pageData + headerSize + slotOff
							+ iter));
				}
				cout << "\n";
				writePage(initDb, pid.pageno);
			}

			else {
				cout << "record is too large\n";
			}
			// Code for update of data goes here
			/*TODO
			 *1.Find the slot to update
			 *2. Check whether the length of that slot is enough to carry the new data
			 *3. If yes update the same slot otherwise create new slot and then insert the data
			 * */
		}

	}

}

char* Heap::getRecord(PageId pid) {

	int pageNum, slotNum, recLen, recOff;
	char* rec;
	Page* initDb;
	pageNum = pid.pageno;
	slotNum = pid.slotno;
	initDb = getPage(pageNum);
	recLen = (*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
			* slotNum)).length;
	recOff = (*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int) - slotSize
			* slotNum)).offset;
	rec = (char*) malloc(recLen + 1);

	if (recOff == -1)
		return NULL;

	else {
		int iter;
		//cout << "record length :" << recLen << "\n";
		for (iter = 0; iter < recLen; iter++) {
			*(rec + iter) = (*(char*) (initDb->pageData + headerSize + recOff
					+ iter));
			//cout <<  *(char*)(initDb->pageData + recOff + headerSize + iter) ;
		}
		//cout << "\n";
		*(rec + iter) = '\0';
		return rec;
	}

}

Status Heap::checkData() {

	Page *initDp, *initDb;
	int firstDpPageNum;
	cout << "inside heap check data" << "\n";
	firstDpPageNum = getFirstDpNum();
	initDp = getPage(firstDpPageNum);
	cout << "Number of DE elements in this DP " << *(int*) (initDp->pageData
			+ sizeof(int)) << endl;
	cout << "Next Dp in this chain " << *(int*) (initDp->pageData) << "\n";
	initDb = getPage(8);
	int length = (*(slot_t*) (initDb->pageData + PAGE_SIZE - slotSize * 2
			- sizeof(int))).length;
	int offset = (*(slot_t*) (initDb->pageData + PAGE_SIZE - slotSize * 2
			- sizeof(int))).offset;
	cout << "length of the data is: " << length << "\n";
	cout << "offset of the slot is : " << offset << "\n";
	cout << "the data at this slot is: ";
	for (int iter = 0; iter < length; iter++) {
		cout << *(char*) (initDb->pageData + offset + headerSize + iter);
	}
	return ok;
}
;

void Heap::openScan(int firstDpPage) {

	Page *initDp;
	int firstDpPageNum, dbPageNum;
	Page *initDb;

	//firstDpPageNum = getFirstDpNum();
	firstDpPageNum = firstDpPage;
	cout << "\t\t1. first Dp page in Dp Chain is " << firstDpPageNum << "\n";
	initDp = getPage(firstDpPageNum);
	cout << "\t\t2. Number of DE elements in this DP Page are "
			<< *(int*) (initDp->pageData + sizeof(int)) << "\n";
	cout << "\t\t3. Next Dp in this chain " << *(int*) (initDp->pageData)
			<< "\n\n\n";

	for (unsigned int iter = 0; iter < MAX_DES; iter++) {

		dbPageNum
				= (*(de*) (initDp->pageData + sizeof(dpH) + iter * sizeof(de))).pageNumber;
		if (dbPageNum == -1) {
			break;
		}

		else {
			cout << "*****************Data for Db page number: " << dbPageNum
					<< " ***************************\n\n";
			initDb = getPage(dbPageNum);
			int length, offset, numOfSlots = *(int*) (initDb->pageData
					+ PAGE_SIZE - sizeof(int));
			cout << "Number of slots are: " << numOfSlots << "\n\n";
			for (int iter = 0; iter < numOfSlots; iter++) {

				if ((*(slot_t*) (initDb->pageData + PAGE_SIZE - sizeof(int)
						- slotSize * (iter + 1))).offset == -1)
					continue;

				length = (*(slot_t*) (initDb->pageData + PAGE_SIZE
						- sizeof(int) - slotSize * (iter + 1))).length;
				offset = (*(slot_t*) (initDb->pageData + PAGE_SIZE
						- sizeof(int) - slotSize * (iter + 1))).offset;
				cout << "Data in slot#" << (iter + 1) << ":   ";
				for (int iter = 0; iter < length; iter++) {
					cout << (*(char*) (initDb->pageData + headerSize + offset
							+ iter));
				}
				cout << "\n";
			}
		}
		cout << "\n\n";
	}
}
;

int Heap::getFirstDpNum() {

	Page *initHd;
	int firstDpPageNum;
	de* initDe;
	initHd = getPage(4);
	initDe = (de*) (initHd->pageData + sizeof(dpH) + 2 * sizeof(de));
	firstDpPageNum = initDe->pageNumber;
	//cout << "firstDpPageNum is: " << firstDpPageNum << endl;
	return firstDpPageNum;

}
;

Page* Heap::getNewPage(int& retPageNum) {

	//database->printSpaceMap();
	Page* page = (Page*) malloc(sizeof(Page));
	page->pageData = (unsigned char*) malloc(sizeof(PAGE_SIZE));
	//database->allocatePage(retPageNum);
	buffer->newPage(retPageNum,page);
	return page;
}


Page* Heap::getPage(int pageNum) {

	Page* page = (Page*) malloc(sizeof(Page));
	//database->readPage(pageNum, page);
	buffer->pinPage(pageNum,page);
	return page;
}

Status Heap::writePage(Page* page, int pageNum) {

	//database->writePage(pageNum, page);
	//buffer->flushPage(pageNum);
	page->dirty = 1;
	buffer->unpinPage(page->pageno,0);
	return ok;
}

time_t Heap::getTimeStamp() {

	time_t seconds;
	seconds = time(NULL);
	return seconds;
}
;

